CDKでデプロイしたLambda Functionsで `Cannot find module '@smithy/service-error-classification'`が発生したので対応した話

CDKでデプロイしたLambda Functionsで `Cannot find module '@smithy/service-error-classification'`が発生したので対応した話

デフォルトのオプションでCDKのNodejsFunctionを使うと@aws-sdk/*と@smithy/*がバンドルされないので注意!!
Clock Icon2024.12.18

リテールアプリ共創部@大阪の岩田です。

とあるプロジェクトでCDKでデプロイしたNode.jsのLambda Functionsで Cannot find module '@smithy/service-error-classification'というエラーが発生したので対応しました。内容的に他にもハマる人が出てきそうなのでブログで共有します。

先に結論

CDKのバージョンv2.161.0から@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackagesという設定値が追加されました。この設定値はデフォルトでtrueとなっており、特定の条件下(※後述します)でcdk deployするとesbuildの引数に --external:@smithy/*が追加され、@smithy/service-error-classificationはバンドル対象外になります。そのためaws-xray-coreを利用している場合はCannot find module '@smithy/service-error-classificationのエラーが発生することになります。

対策ですが@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackagesをfalseに設定する...のではなくbundleAwsSDKをtrueに設定するのが良いでしょう。

環境

今回利用した各種ライブラリのバージョンは以下のとおりです。

  • aws-cdk-lib: 2.172.0
  • aws-xray-sdk-core: 3.10.2
  • aws-xray-sdk-fetch: 3.10.2

経緯など

ここからはエラーが発生するようになった経緯と調査の過程について共有します。

aws-xray-sdk-fetchを追加

対象のLambda FunctionはBFF的な処理を行うものでした。fetchを利用した外部APIの呼び出しが多いため、オブザーバビリティを担保するために aws-xray-sdk-fetchを導入して外部APIの呼び出しをトレースできるようにした方が良いという話をし、aws-xray-sdk-fetchを導入することになりました。

以下のようなイメージです。

import { captureFetchGlobal } from 'aws-xray-sdk-fetch';
const capturedFetch = captureFetchGlobal()
...const res = await capturedFetch('https://dev.classmethod.jp')

これでfetchがトレース可能になりました!

cdk deployすると...動かない

トレースを仕込んだのでcdk deployすると...Lambdaが動きません。以下のようなエラーログが出力されており、 @smithy/service-error-classificationが見つからないようです。

2024-12-17T03:27:58.686Z	undefined	ERROR	Uncaught Exception 	{
    "errorType": "Runtime.ImportModuleError",
    "errorMessage": "Error: Cannot find module '@smithy/service-error-classification'\nRequire stack:\n- /var/task/index.js\n- /var/runtime/index.mjs",
    "stack": [
        "Runtime.ImportModuleError: Error: Cannot find module '@smithy/service-error-classification'",
        "Require stack:",
        "- /var/task/index.js",
        "- /var/runtime/index.mjs",
        "    at _loadUserApp (file:///var/runtime/index.mjs:1087:17)",
        "    at async UserFunction.js.module.exports.load (file:///var/runtime/index.mjs:1119:21)",
        "    at async start (file:///var/runtime/index.mjs:1282:23)",
        "    at async file:///var/runtime/index.mjs:1288:1"
    ]
}

aws-xray-sdk-fetchaws-xray-sdk-coreに依存しており、aws-xray-sdk-core@smithy/service-error-classificationに依存しているのですが、なぜ@smithy/service-error-classificationが見つからないのか...

cdk synthで生成されるアセットに @smithy/service-error-classificationが含まれていない

原因の切り分けとして aws-xray-sdk-fetch を利用している他のプロジェクトと比較したところ、cdk synthで生成されるcdk.out/asset...略/index.jsというアセットがおかしいことが分かりました。正常に動作しているプロジェクトではアセットの中身が以下のようになっていました。

// node_modules/@smithy/service-error-classification/dist-cjs/index.js
var require_dist_cjs = __commonJS({
  "node_modules/@smithy/service-error-classification/dist-cjs/index.js"(exports2, module2) {
    var __defProp2 = Object.defineProperty;
    var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
    var __getOwnPropNames2 = Object.getOwnPropertyNames;
    var __hasOwnProp2 = Object.prototype.hasOwnProperty;
    var __name = (target, value) => __defProp2(target, "name", { value, configurable: true });
    var __export2 = (target, all) => {
      for (var name in all)
        __defProp2(target, name, { get: all[name], enumerable: true });
    };
...

@smithy/service-error-classificationの中身がバンドルされていることが分かります。一方でエラーが発生しているプロジェクトではアセット内に上記のようなコードが出力されていませんでした。何かしらの要因で @smithy/service-error-classificationがバンドル対象外となっているようです。

externalModulesは指定していないが...??

対象のLambda FunctionをデプロイするCDKコードを確認したところ、以下のような何の変哲も無い実装でした。

const restApiFunc = new aws_lambda_nodejs.NodejsFunction(
  this,
  'RestApiFunc',
  {
    architecture: aws_lambda.Architecture.ARM_64,
    runtime: aws_lambda.Runtime.NODEJS_22_X,
    entry: 'index.ts',
    memorySize: 1769,
    timeout: Duration.seconds(29),
    tracing: aws_lambda.Tracing.ACTIVE,
  },
);

てっきりexternalModules@smithy/service-error-classificationが指定されているのかと思いましたがそうでは無さそうです。なぜ...

esbuildを手動実行するとバンドルされた

原因切り分けのためにCDKを介さず手動で./node_modules/.bin/esbuild --platform=node --bundle src/index.tsを実行したところ、今度は無事に@smithy/service-error-classificationがバンドルされました。ということはCDKからesbuildを呼び出すあたりに原因がありそうです。

ログを仕込む

CDKがesbuildをどのように呼び出してるのか特定したかったので、力技で node_modules/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.js のコードにログ出力のロジックを追加しました。

// ...略
if(!Bundling.esbuildInstallation.version.startsWith(`${ESBUILD_MAJOR_VERSION}.`))throw new Error(`Expected esbuild version ${ESBUILD_MAJOR_VERSION}.x but got ${Bundling.esbuildInstallation.version}`);

const localCommand=createLocalCommand(outputDir,Bundling.esbuildInstallation,Bundling.tscInstallation);
// 追加↓
console.error(localCommand);
// 追加↑
return(0,util_1().exec)(osPlatform==="win32"?"cmd":"bash",[osPlatform==="win32"?"/c":"-c",localCommand],{env:{...process.env,...environment},stdio:["ignore",process.stderr,"inherit"],cwd,windowsVerbatimArguments:osPlatform==="win3

node_modulesの中身なのでトランスパイルされており、編集するのに気を使いました。この状態でcdk synthを実行すると以下のような出力が得られました。

npx --no-install esbuild --bundle "...略/index.ts" --target=node22 --platform=node --outfile="...略/cdk.out/bundling-temp-...略/index.js" --external:@aws-sdk/* --external:@smithy/*

esbuildのオプションに--external:@smithy/*が設定されていることが分かります。ついでに言うと--external:@aws-sdk/* も設定されていますね。

CDKのコードを確認

大体原因にあたりがついたのでCDKのコードを確認します。

この辺りでexternal@smithy/*を指定しているようです。

https://github.com/aws/aws-cdk/blob/87e21d625af86873716734dd5568940d41096c45/packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts#L136-L137

cdk.FeatureFlags.of(scope).isEnabled(LAMBDA_NODEJS_SDK_V3_EXCLUDE_SMITHY_PACKAGES)でチェックしていることからcdk.json@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackagesをtrueに設定すれば良さそうですね。ここを修正して再度cdk synthすると無事に@smithy/service-error-classificationもバンドルされるようになりました🎉

コミット履歴を調べると上記の設定値は以下のコミットで追加されたようです。

https://github.com/aws/aws-cdk/commit/19ee46d7653894f0669aff3872c6c5314be0666c#diff-d3a78437ce44ac758a33052c1ce663976f465681199c2c85cc42b8b57832b08e

元になったissueは以下です。

https://github.com/aws/aws-cdk/issues/31610

AWS SDKはバンドル対象外になっているのに@smithy系のパッケージがバンドルされていたため、Lambda実行環境のAWS CDKとバンドルした@smithy系の互換性の問題でエラーが発生していたそうです。

そしてこのissueを見て気付いたのですが、NodejsFunctionのデフォルト設定を使うとAWS SDKはバンドルされないんですね...

Lambdaの公式ドキュメントには以下のように記載されており、Lambda実行環境に組み込まれているAWS SDKを使うことのリスクについて示唆しています。

Control the dependencies in your function's deployment package. The AWS Lambda execution environment contains a number of libraries. For the Node.js and Python runtimes, these include the AWS SDKs. To enable the latest set of features and security updates, Lambda will periodically update these libraries. These updates may introduce subtle changes to the behavior of your Lambda function. To have full control of the dependencies your function uses, package all of your dependencies with your deployment package.

https://docs.aws.amazon.com/lambda/latest/dg/nodejs-handler.html#nodejs-best-practices

ということで @smithy系のパッケージだけでなくAWS SDKについてもバンドル対象とするようにNodejsFunctionの呼び出しを修正しました。

const restApiFunc = new aws_lambda_nodejs.NodejsFunction(
  this,
  'RestApiFunc',
  {
    architecture: aws_lambda.Architecture.ARM_64,
    runtime: aws_lambda.Runtime.NODEJS_22_X,
    entry: 'index.ts',
    bundling: {          
      bundleAwsSDK: true,
    },
    memorySize: 1769,
    timeout: Duration.seconds(29),
    tracing: aws_lambda.Tracing.ACTIVE,
  },
);

bundleAwsSDKがtrueの場合はdefaultExternals[]になるようです。

https://github.com/aws/aws-cdk/blob/87e21d625af86873716734dd5568940d41096c45/packages/aws-cdk-lib/aws-lambda-nodejs/lib/bundling.ts#L146

なのでbundleAwsSDKにtrueを指定している場合はcdk.json@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackagesはtrue/falseどっちでも問題無さそうですね。ということで記事の冒頭に記載した「特定の条件下」というのはbundleAwsSDKが未指定もしくはfalseの場合になります。

まとめ

Cannot find module '@smithy/service-error-classification'のエラーを解消した経緯についてご紹介しました。個人的にはNodejsFunctionがデフォルトでAWS CDKをバンドルしないというのが衝撃でした。これまでデプロイしてきたLambdaのAWS CDKが意図通りのバージョンになっていない可能性が高そうなので改めてチェックしてみます。

参考

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.